// SPDX-License-Identifier: Apache-2.0
import { isArray } from "@thi.ng/checks/is-array";
import { GLSLVersion } from "@thi.ng/shader-ast-glsl/api";
import type { GLSL } from "./api/glsl.js";
import type { GLSLDeclPrefixes, GLSLSyntax } from "./api/shader.js";

export const PREFIXES: GLSLDeclPrefixes = {
	a: "a_",
	v: "v_",
	u: "u_",
	o: "o_",
};

export const NO_PREFIXES: GLSLDeclPrefixes = {
	a: "",
	v: "",
	u: "",
	o: "",
};

/**
 * GLSL data declaration code generators.
 */
export const SYNTAX: Record<GLSLVersion, GLSLSyntax> = {
	/**
	 * WebGL (GLSL ES 1.0)
	 */
	[GLSLVersion.GLES_100]: {
		number: 100,
		attrib: (id, type, pre) =>
			`attribute ${isArray(type) ? type[0] : type} ${pre.a}${id};`,
		varying: {
			vs: (id, type, pre) => __arrayDecl("varying", type, pre.v + id),
			fs: (id, type, pre) => __arrayDecl("varying", type, pre.v + id),
		},
		uniform: (id, u, pre) => __arrayDecl("uniform", <any>u, pre.u + id),
		output: (id, type, pre) =>
			isArray(type)
				? `#define ${pre.o}${id} gl_FragData[${type[1]}]`
				: "",
	},
	/**
	 * WebGL 2 (GLSL ES 3)
	 */
	[GLSLVersion.GLES_300]: {
		number: 300,
		attrib: (id, type, pre) =>
			isArray(type)
				? `layout(location=${type[1]}) in ${type[0]} ${pre.a}${id};`
				: `in ${type} ${pre.a}${id};`,
		varying: {
			vs: (id, type, pre) => __arrayDecl("out", type, pre.v + id),
			fs: (id, type, pre) => __arrayDecl("in", type, pre.v + id),
		},
		uniform: (id, u, pre) => __arrayDecl("uniform", <any>u, pre.u + id),
		output: (id, type, pre) =>
			isArray(type)
				? `layout(location=${type[1]}) out ${type[0]} ${pre.o}${id};`
				: `out ${type} ${pre.o}${id};`,
	},
};

/** @internal */
const __arrayDecl = (
	qualifier: string,
	decl: GLSL | [GLSL, number],
	id: string
) => {
	const type = isArray(decl) ? decl[0] : decl;
	return type.indexOf("[]") > 0
		? `${qualifier} ${type.replace("[]", "")} ${id}[${
				(<[GLSL, number]>decl)[1]
		  }];`
		: `${qualifier} ${type} ${id};`;
};

/**
 * GLSL preprocessor macro for conditional execution based on `__VERSION__`.
 *
 * @param ver -
 * @param ok -
 * @param fail -
 */
export const VERSION_CHECK = (ver: number, ok: string, fail = "") => {
	let cmp = ">=";
	if (!ok) {
		ok = fail;
		fail = <any>null;
		cmp = "<";
	}
	return `#if __VERSION__ ${cmp} ${ver}
${ok}${fail ? `\n#else\n${fail}` : ""}
#endif`;
};

export const ALIAS_TEXTURE = VERSION_CHECK(
	300,
	"",
	"#define texture texture2D"
);

/**
 * GLSL version specific fragment shader output. If `__VERSION__ >= 300`
 * assigns `body` to `out`, else to `gl_FragColor`.
 *
 * @param body -
 * @param out -
 */
export const EXPORT_FRAGCOL = (body = "col", out = "o_fragColor") =>
	VERSION_CHECK(300, `${out}=${body};`, `gl_FragColor=${body};`);

/**
 * Default GLSL prelude.
 */
export const GLSL_HEADER = `#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp int;
precision highp float;
#else
precision mediump int;
precision mediump float;
#endif
${VERSION_CHECK(300, "precision lowp sampler3D;")}
#ifndef PI
#define PI 3.141592653589793
#endif
#ifndef TAU
#define TAU 6.283185307179586
#endif
#ifndef HALF_PI
#define HALF_PI 1.570796326794896
#endif
`;
